﻿//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  License: GNU General Public License version 3 (GPLv3)
//
//  Email: pavel_torgashov@mail.ru.
//
//  Copyright (C) Torgashov Pavel, 2011. 

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.ComponentModel;

namespace FastColoredTextBoxNS
{
    /// <summary>
    /// Fast colored textbox
    /// </summary>
    public class FastColoredTextBox : UserControl
    {
        readonly CommandManager manager = new CommandManager();
        bool shiftKeyIsPressed = false;
        Color selectionColor;
        bool wordWrap;
        int wordWrapLinesCount;
        Range selection;
        FindForm findForm;
        ReplaceForm replaceForm;
        internal readonly List<Line> lines = new List<Line>();
        internal bool needRecalc;
        readonly Dictionary<FontStyle, Font> fontsByStyle = new Dictionary<FontStyle, Font>();
        /// <summary>
        /// Height of char in pixels
        /// </summary>
        public int CharHeight { get; private set; }
        /// <summary>
        /// Width of char in pixels
        /// </summary>
        public int CharWidth { get; private set; }
        /// <summary>
        /// Spaces count for tab
        /// </summary>
        [DefaultValue(4)]
        public int TabLength { get; set; }
        /// <summary>
        /// Text was changed
        /// </summary>
        [Browsable(false)]
        public bool IsChanged { get; set; }
        /// <summary>
        /// Text version
        /// </summary>
        /// <remarks>This counter is incremented each time changes the text</remarks>
        [Browsable(false)]
        public int TextVersion { get; private set; }
        /// <summary>
        /// Read only
        /// </summary>
        [DefaultValue(false)]
        public bool ReadOnly { get; set; }
        /// <summary>
        /// Shows line numbers
        /// </summary>
        [DefaultValue(false)]
        public bool ShowLineNumber { get; set; }
        /// <summary>
        /// Color of line numbers
        /// </summary>
        [DefaultValue(typeof(Color), "Teal")]
        public Color LineNumberColor { get; set; }
        /// <summary>
        /// Color of line numbers
        /// </summary>
        [DefaultValue(typeof(Color), "White")]
        public Color IndentBackColor { get; set; }
        /// <summary>
        /// Left indent in pixels
        /// </summary>
        [DefaultValue(0)]
        public int LeftIndent { get; set; }
        /// <summary>
        /// Style dictionary
        /// Key is Style
        /// Value is styleId
        /// </summary>
        public readonly Dictionary<Style, UInt16> StyleToCode = new Dictionary<Style, UInt16>();
        /// <summary>
        /// Styles
        /// Index is styleId
        /// </summary>
        public readonly List<Style> Styles = new List<Style>();
        /// <summary>
        /// TextChanged event
        /// It occurs after insert, delete, clear, undo and redo operations
        /// </summary>
        [Browsable(true)]
        public new event EventHandler<TextChangedEventArgs> TextChanged;
        /// <summary>
        /// TextChanging event
        /// It occurs before insert, delete, clear, undo and redo operations
        /// </summary>
        [Browsable(true)]
        public event EventHandler TextChanging;
        /// <summary>
        /// SelectionChanged event
        /// It occurs after change of selection
        /// </summary>
        [Browsable(true)]
        public event EventHandler SelectionChanged;

        /// <summary>
        /// Constructor
        /// </summary>
        public FastColoredTextBox()
        {
            //drawing optimization
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            //append monospace font
            Font f = new Font("Consolas", 9.75f, FontStyle.Regular, GraphicsUnit.Point);
            if (f.Name != "Consolas")
                f = new Font(FontFamily.GenericMonospace, 9.75f, FontStyle.Regular, GraphicsUnit.Point);
            Font = f;
            //create one line
            lines.Add(new Line());
            Selection = new Range(this) { Start = new Place(0, 0) };
            //
            SelectionColor = Color.FromArgb(50, Color.Blue);
            Cursor = Cursors.IBeam;
            BackColor = Color.White;
            LeftIndent = 0;
            LineNumberColor = Color.Teal;
            IndentBackColor = Color.White;
            //
            base.AutoScroll = true;
            //
            InitDefaultStyle();
        }

        /// <summary>
        /// Current selection range
        /// </summary>
        [Browsable(false)]
        public Range Selection {
            get { return selection; }
            set
            {
                selection = value;
                Invalidate();
            }
        }

        /// <summary>
        /// Background color
        /// </summary>
        [DefaultValue(typeof(Color), "White")]
        public override Color BackColor
        {
            get
            {
                return base.BackColor;
            }
            set
            {
                base.BackColor = value;
            }
        }

        /// <summary>
        /// WordWrap
        /// </summary>
        [Browsable(true)]
        [DefaultValue(false)]
        public bool WordWrap
        {
            get { return wordWrap; }
            set {
                if(wordWrap == value) return;
                wordWrap = value;
                RecalcWordWrap(0, LinesCount-1);
                Invalidate();
            }
        }

        /// <summary>
        /// Count of lines with wordwrap effect
        /// </summary>
        [Browsable(false)]
        public int WordWrapLinesCount
        {
            get{
                if (needRecalc)
                    Recalc();
                return wordWrapLinesCount;
            }
        }

        /// <summary>
        /// Do not change this property
        /// </summary>
        [Browsable(false)]
        public override bool AutoScroll 
        { 
            get { return base.AutoScroll; } 
            set { ;}
        }

        /// <summary>
        /// Count of lines
        /// </summary>
        [Browsable(false)]
        public int LinesCount
        {
            get { return lines.Count; }
        }

        /// <summary>
        /// Gets or sets char and styleId for given place
        /// This property does not fire OnTextChanged event
        /// </summary>
        public Char this[Place place]
        {
            get { return lines[place.iLine][place.iChar]; }
            set { lines[place.iLine][place.iChar] = value; }
        }

        /// <summary>
        /// Shows find dialog
        /// </summary>
        public void ShowFindDialog()
        {
            if (findForm == null)
                findForm = new FindForm(this);
            findForm.Show();
        }

        /// <summary>
        /// Shows replace dialog
        /// </summary>
        public void ShowReplaceDialog()
        {
            if (replaceForm == null)
                replaceForm = new ReplaceForm(this);
            replaceForm.Show();
        }

        /// <summary>
        /// Gets length of given line
        /// </summary>
        /// <param name="iLine">Line index</param>
        /// <returns>Length of line</returns>
        public int GetLineLength(int iLine)
        {
            if (iLine < 0 || iLine >= lines.Count)
                throw new ArgumentOutOfRangeException("Line index out of range");

            return lines[iLine].Count;
        }

        /// <summary>
        /// Get range of line
        /// </summary>
        /// <param name="iLine">Line index</param>
        public Range GetLine(int iLine)
        {
            if (iLine < 0 || iLine >= lines.Count) 
                throw new ArgumentOutOfRangeException("Line index out of range");

            Range sel = new Range(this);
            sel.Start = new Place(0, iLine);
            sel.End = new Place(lines[iLine].Count, iLine);
            return sel;
        }

        /// <summary>
        /// Color of selection area
        /// </summary>
        /// <remarks>Alpha component must be less then 200</remarks>
        public Color SelectionColor
        {
            get
            {
                return selectionColor;
            }
            set
            {
                selectionColor = value;
                if (selectionColor.A >= 200)
                    selectionColor = Color.FromArgb(50, selectionColor);
            }
        }

        /// <summary>
        /// Copy selected text into Clipboard
        /// </summary>
        public void Copy()
        {
            if(Selection.End != Selection.Start)
                Clipboard.SetText(Selection.Text);
        }      
        
        /// <summary>
        /// Cut selected text into Clipboard
        /// </summary>
        public void Cut()
        {
            if (Selection.End != Selection.Start)
            {
                Clipboard.SetText(Selection.Text);
                ClearSelected();
            }
        }

        /// <summary>
        /// Paste text from clipboard into selection position
        /// </summary>
        public void Paste()
        {
            if (Clipboard.ContainsText())
                InsertText(Clipboard.GetText());
        }

        /// <summary>
        /// Text of control
        /// </summary>
        [Browsable(true)]
        public override string Text
        {
            get {
                Range sel = new Range(this);
                sel.SelectAll();
                return sel.Text;
           }

            set {
                Selection.BeginUpdate();
                try
                {
                    Clear();
                    InsertText(value);
                    GoHome();
                }
                finally { Selection.EndUpdate(); }
            }
        }

        /// <summary>
        /// Select all chars of text
        /// </summary>
        public void SelectAll()
        {
            Selection.SelectAll();
        }

        /// <summary>
        /// Text of current selection
        /// </summary>
        [Browsable(false)]
        public string SelectedText
        {
            get { return Selection.Text; }
            set { InsertText(value); }
        }

        /// <summary>
        /// Start position of selection
        /// </summary>
        [Browsable(false)]
        [DefaultValue(0)]
        public int SelectionStart
        {
            get {
                return Math.Min(PlaceToPosition(Selection.Start), PlaceToPosition(Selection.End));
            }
            set {
                Selection.Start = PositionToPlace(value);
            }
        }

        /// <summary>
        /// Length of selected text
        /// </summary>
        [Browsable(false)]
        [DefaultValue(0)]
        public int SelectionLength
        {
            get
            {
                return Math.Abs(PlaceToPosition(Selection.Start) - PlaceToPosition(Selection.End));
            }
            set
            {
                if(value>0)
                    Selection.End = PositionToPlace(SelectionStart + value);
            }
        }

        /// <summary>
        /// Move caret to end of text
        /// </summary>
        public void GoEnd()
        {
            if (lines.Count > 0)
                Selection.Start = new Place(lines[lines.Count - 1].Count, lines.Count - 1);
            else
                Selection.Start = new Place(0, 0);
            DoCaretVisible();
        }

        /// <summary>
        /// Move caret to first position
        /// </summary>
        public void GoHome()
        {
            Selection.Start = new Place(0, 0);
            VerticalScroll.Value = 0;
            HorizontalScroll.Value = 0;
        }

        /// <summary>
        /// Clear text, styles, history, caches
        /// </summary>
        public void Clear()
        {
            Selection.BeginUpdate();
            try
            {
                Selection.SelectAll();
                ClearSelected();
                manager.ClearHistory();
                StyleToCode.Clear();
                Styles.Clear();
                InitDefaultStyle();
                Invalidate();
            }
            finally { Selection.EndUpdate(); }
        }

        /// <summary>
        /// Clear style of all text
        /// </summary>
        public void ClearAllStyles()
        {
            foreach (var line in lines)
                for (int i = 0; i < line.Count;i++)
                {
                    Char c = line[i];
                    c.styleId = 0;
                    line[i] = c;
                }
            Invalidate();
        }

        /// <summary>
        /// Clears undo and redo stacks
        /// </summary>
        public void ClearUndo()
        {
            manager.ClearHistory();
        }

        /// <summary>
        /// Clear style of line
        /// </summary>
        /// <param name="iLine">Line index</param>
        public void ClearLineStyle(int iLine)
        {
            if (iLine < 0 || iLine >= lines.Count)
                throw new ArgumentOutOfRangeException("Line index out of range");
            var line = lines[iLine];
            for (int i = 0; i < line.Count; i++)
            {
                Char c = line[i];
                c.styleId = 0;
                line[i] = c;
            }
        }

        private void InitDefaultStyle()
        {
            Style defaultStyle = new Style(Color.Transparent, Color.Transparent, FontStyle.Regular);
            Styles.Add(defaultStyle);
            StyleToCode.Add(defaultStyle, 0);
        }

        /// <summary>
        /// Insert text into current selection position
        /// </summary>
        /// <param name="text"></param>
        public void InsertText(string text)
        {
            if (text == null)
                return;

            ClearSelected();
            //create of char
            manager.ExecuteCommand(new InsertTextCommand(this, text));
            DoCaretVisible();
            //
            Invalidate();
        }

        /// <summary>
        /// Font
        /// </summary>
        /// <remarks>Use monospace font</remarks>
        public override Font Font
        {
            get
            {
                return base.Font;
            }
            set
            {
                base.Font = value;
               
                SizeF size = GetCharSize(base.Font, 'M');
                CharHeight = (int)Math.Round(size.Height * 0.9f);
                CharWidth = (int)Math.Round(size.Width * 0.85f);
            }
        }

        internal UInt16 GetStyleCode(Style style)
        {
            UInt16 code;
            if (!StyleToCode.TryGetValue(style, out code))
            {
                code = (UInt16)Styles.Count;
                Styles.Add(style);
                StyleToCode.Add(style, code);
            }
            return code;
        }

        SizeF GetCharSize(Font font, char c)
        {
            Size sz2 = TextRenderer.MeasureText("<" + c.ToString() + ">", font);
            Size sz3 = TextRenderer.MeasureText("<>", font);

            return new SizeF(sz2.Width - sz3.Width + 1, sz2.Height);
        }

        protected override void OnScroll(ScrollEventArgs se)
        {
            base.OnScroll(se);
            Invalidate();
        }

        void InsertChar(char c)
        {
            manager.ExecuteCommand(new InsertCharCommand(this, c));
        }

        /// <summary>
        /// Deletes selected chars
        /// </summary>
        public void ClearSelected()
        {
            if (Selection.Start != Selection.End)
            {
                manager.ExecuteCommand(new ClearSelectedCommand(this));
                Invalidate();
            }
        }

        private void Recalc()
        {
            if (!needRecalc)
                return;

            needRecalc = false;
            //calc min left indent
            int charsForLineNumber = 2+(LinesCount>0?(int)Math.Log10(LinesCount):0);
            if (ShowLineNumber && LeftIndent < charsForLineNumber * CharWidth)
                LeftIndent = charsForLineNumber * CharWidth;
            //calc max line length and count of wordWrapLines
            int maxLineLength = 0;
            wordWrapLinesCount = 0;
            foreach (var line in lines)
            {
                if (line.Count > maxLineLength)
                    maxLineLength = line.Count;

                wordWrapLinesCount += line.WordWrapStringsCount;
            }
            
            //adjust AutoScrollMinSize
            AutoScrollMinSize = new Size(wordWrap ? 0 : LeftIndent + (maxLineLength) * CharWidth + 2, wordWrapLinesCount * CharHeight);
        }

        private void RecalcWordWrap(int fromLine, int toLine)
        {
            int maxCharsPerLine = (ClientSize.Width - LeftIndent) / CharWidth;
            for (int iLine = fromLine; iLine <= toLine; iLine++)
                lines[iLine].CalcCutOffs(maxCharsPerLine);
            needRecalc = true;
        }

        protected override void OnClientSizeChanged(EventArgs e)
        {
            base.OnClientSizeChanged(e);
            if (WordWrap)
            {
                RecalcWordWrap(0, lines.Count - 1);
                Invalidate();
            }
        }

        /// <summary>
        /// Scroll control for display defined rectangle
        /// </summary>
        /// <param name="rect"></param>
        private void DoVisibleRectangle(Rectangle rect)
        {
            int v = VerticalScroll.Value;
            int h = HorizontalScroll.Value;

            if (rect.Bottom > ClientRectangle.Height)
                v += rect.Bottom - ClientRectangle.Height;
            else
                if (rect.Top < 0)
                    v += rect.Top;

            if (rect.Right > ClientRectangle.Width)
                h += rect.Right - ClientRectangle.Width;
            else
                if (rect.Left < LeftIndent)
                    h += rect.Left - LeftIndent;
            //
            try
            {
                VerticalScroll.Value = Math.Max(0,v);
                HorizontalScroll.Value = Math.Max(0,h);
            }
            catch (ArgumentOutOfRangeException) { ;}

            //some magic for update scrolls
            AutoScrollMinSize -= new Size(1, 0);
            AutoScrollMinSize += new Size(1, 0);
        }

        protected override void OnKeyPress(KeyPressEventArgs e)
        {
            base.OnKeyPress(e);

            if (e.Handled)
                return;

            if (char.IsControl(e.KeyChar) && e.KeyChar != '\r' && e.KeyChar != '\t')
                return;

            if (ReadOnly)
                return;

            //remove selected chars
            ClearSelected();

            //replace \r on \n
            char c = e.KeyChar;
            if (c == '\r')
                c = '\n';

            //tab?
            if (c == '\t')
            {
                //insert tab as spaces
                int tabLength = TabLength>0?TabLength:4;
                int spaces = tabLength - (Selection.Start.iChar % tabLength);
                InsertText(new String(' ', spaces));
            }
            else
                //insert char
                InsertChar(c);

            DoCaretVisible();
            //
            Invalidate();
        }

        /// <summary>
        /// Scroll control for display caret
        /// </summary>
        public void DoCaretVisible()
        {
            Recalc();
            Point car = PlaceToPoint(Selection.Start);
            car.Offset(-CharWidth, 0);
            DoVisibleRectangle(new Rectangle(car, new Size(2  * CharWidth, 2 * CharHeight)));
        }

        /// <summary>
        /// Scroll control for display selection area
        /// </summary>
        public void DoSelectionVisible()
        {
            Recalc();
            DoVisibleRectangle(new Rectangle(PlaceToPoint(new Place(0, Selection.End.iLine)), new Size(2 * CharWidth, 2 * CharHeight)));
            Point car = PlaceToPoint(Selection.Start);
            Point car2 = PlaceToPoint(Selection.End);
            car.Offset(-CharWidth, 0);
            DoVisibleRectangle(new Rectangle(car, new Size(Math.Abs(car2.X-car.X), Math.Abs(car2.Y-car.Y))));
        }

        protected override void OnKeyUp(KeyEventArgs e)
        {
            base.OnKeyUp(e);

            if (e.KeyCode == Keys.ShiftKey)
                shiftKeyIsPressed = false;
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);

            if (e.KeyCode == Keys.ShiftKey)
            {
                shiftKeyIsPressed = true;
                return;
            }

            if (e.Handled)
                return;

            switch (e.KeyCode)
            {
                case Keys.F:
                    if (e.Modifiers == Keys.Control)
                        ShowFindDialog();
                    break;
                case Keys.H:
                    if (e.Modifiers == Keys.Control)
                        ShowReplaceDialog();
                    break;
                case Keys.C:
                    if (e.Modifiers == Keys.Control)
                        Copy();
                    break;
                case Keys.X:
                    if (e.Modifiers == Keys.Control && !ReadOnly)
                        Cut();
                    break;
                case Keys.V:
                    if (e.Modifiers == Keys.Control && !ReadOnly)
                        Paste();
                    break;
                case Keys.A:
                    if (e.Modifiers == Keys.Control)
                        Selection.SelectAll();
                    break;
                case Keys.Z:
                    if (e.Modifiers == Keys.Control && !ReadOnly)
                        Undo();
                    break;
                case Keys.R:
                    if (e.Modifiers == Keys.Control && !ReadOnly)
                        Redo();
                    break;

                case Keys.Back:
                    if (ReadOnly) break;
                    if (e.Modifiers == Keys.Alt)
                        Undo();
                    else
                    if(e.Modifiers == Keys.None)
                    {
                        if (Selection.End != Selection.Start)
                            ClearSelected();
                        else
                            InsertChar('\b');
                    }

                    break;
                case Keys.Delete:
                    if (ReadOnly) break;
                    if (Selection.End != Selection.Start)
                        ClearSelected();
                    else
                    {
                        var p = Selection.Start;
                        Selection.GoRight(false);
                        if(p!=Selection.Start)
                            InsertChar('\b');
                    }
                    break;

                case Keys.Left:
                    Selection.GoLeft(e.Shift);
                    break;
                case Keys.Right:
                    Selection.GoRight(e.Shift);
                    break;
                case Keys.Up:
                    Selection.GoUp(e.Shift);
                    break;
                case Keys.Down:
                    Selection.GoDown(e.Shift);
                    break;
                case Keys.PageUp:
                    Selection.GoPageUp(e.Shift);
                    break;
                case Keys.PageDown:
                    Selection.GoPageDown(e.Shift);
                    break;
                case Keys.Home:
                    Selection.GoHome(e.Shift);
                    break;
                case Keys.End:
                    Selection.GoEnd(e.Shift);
                    break;
                default:
                    if ((e.Modifiers & Keys.Control) != 0)
                        return;
                    if ((e.Modifiers & Keys.Alt) != 0)
                        return;
                    break;
            }

            DoCaretVisible();
            Invalidate();
        }


        /// <summary>
        /// Undo last operation
        /// </summary>
        public void Undo()
        {
            manager.Undo();
            Invalidate();
        }

        /// <summary>
        /// Is undo enabled?
        /// </summary>
        [Browsable(false)]
        public bool UndoEnabled
        {
            get { return manager.UndoEnabled; }
        }

        /// <summary>
        /// Redo
        /// </summary>
        public void Redo()
        {
            manager.Redo();
            Invalidate();
        }

        /// <summary>
        /// Is redo enabled?
        /// </summary>
        [Browsable(false)]
        public bool RedoEnabled
        {
            get { return manager.RedoEnabled; }
        }

        protected override bool IsInputKey(Keys p_Keys)
        {
            switch (p_Keys)
            {
                case Keys.Up:
                case Keys.Down:
                case Keys.Left:
                case Keys.Right:
                case Keys.Tab:
                case Keys.Down | Keys.Shift:
                case Keys.Up | Keys.Shift:
                case Keys.Left | Keys.Shift:
                case Keys.Right | Keys.Shift:
                    return true;
            }
            return base.IsInputKey(p_Keys);
        }

        /// <summary>
        /// Draw control
        /// </summary>
        protected override void OnPaint(PaintEventArgs e)
        {
            if (needRecalc)
                Recalc();
            //
            SolidBrush SelectionBrush;
            if (Focused)
                SelectionBrush = new SolidBrush(SelectionColor);
            else
                SelectionBrush = new SolidBrush(Color.FromArgb(SelectionColor.A / 2, SelectionColor));
            //
            SolidBrush LineNumberBrush = new SolidBrush(LineNumberColor);
            //draw indent area
            e.Graphics.FillRectangle(new SolidBrush(IndentBackColor), 0, 0, LeftIndent - 1, ClientSize.Height);
            e.Graphics.DrawLine(Pens.Silver, LeftIndent - 1, 0, LeftIndent - 1, ClientSize.Height);
            //
            int firstChar = HorizontalScroll.Value / CharWidth;
            int lastChar = (HorizontalScroll.Value + ClientSize.Width) / CharWidth;
            //draw chars
            int linesCounter = 0;
            for (int iLine = 0; iLine < lines.Count; iLine++)
            {
                //
                if (linesCounter * CharHeight > VerticalScroll.Value + ClientSize.Height)
                    break;
                linesCounter += lines[iLine].WordWrapStringsCount;
                if (linesCounter * CharHeight < VerticalScroll.Value)
                    continue;
                Line line = lines[iLine];
                //draw wordwrap strings of line
                for (int iWordWrapLine = 0; iWordWrapLine < line.WordWrapStringsCount; iWordWrapLine++)
                {
                    int y = (linesCounter - line.WordWrapStringsCount + iWordWrapLine) * CharHeight - VerticalScroll.Value;
                    int from = line.GetWordWrapStringStartPosition(iWordWrapLine);
                    int to = line.GetWordWrapStringFinishPosition(iWordWrapLine);

                    for (int iChar = firstChar; iChar <= Math.Min(to - from, lastChar); iChar++)
                    {
                        int x = LeftIndent + iChar * CharWidth - HorizontalScroll.Value;
                        if (x < LeftIndent) continue;
                        Style style = Styles[line[from + iChar].styleId];
                        Font f;
                        if (!fontsByStyle.TryGetValue(style.fontStyle, out f))
                        {
                            f = new Font(Font, style.fontStyle);
                            fontsByStyle[style.fontStyle] = f;
                        }
                        if (style.foreColor == Color.Transparent)
                            style.foreColor = ForeColor;
                        //draw background
                        if (style.bgColor != Color.Transparent)
                            e.Graphics.FillRectangle(new SolidBrush(style.bgColor), x, y, CharWidth, CharHeight);
                        //draw char
                        int dx = 2;
                        int dy = 2;
                        e.Graphics.DrawString(line[from + iChar].c.ToString(), f, new SolidBrush(style.foreColor), x - dx, y - dy);
                        //draw selection
                        if (Selection.End != Selection.Start)
                            if (Selection.Contains(new Place(from + iChar, iLine)))
                                e.Graphics.FillRectangle(SelectionBrush, x, y, CharWidth, CharHeight);
                    }
                    //draw line number
                    if (ShowLineNumber && iWordWrapLine==0)
                        e.Graphics.DrawString((iLine+1).ToString(), Font, LineNumberBrush, 0, y);
                }
            }
            //draw caret
            if (Focused)
            {
                Point car = PlaceToPoint(Selection.Start);
                if(car.X>=LeftIndent)
                    e.Graphics.DrawLine(Pens.Black, car.X, car.Y, car.X, car.Y + CharHeight);
            }
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);

            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                var oldEnd = Selection.End;
                Selection.Start = PointToPlace(e.Location);
                if (shiftKeyIsPressed)
                    Selection.End = oldEnd;
                Invalidate();
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                var oldEnd = Selection.End;
                Selection.Start = PointToPlace(e.Location);
                Selection.End = oldEnd;
                DoCaretVisible();
                Invalidate();
            }
        }

        protected override void OnMouseDoubleClick(MouseEventArgs e)
        {
            base.OnMouseDoubleClick(e);

            Place p = PointToPlace(e.Location);
            int fromX = p.iChar;
            int toX = p.iChar;

            for(int i=p.iChar;i<lines[p.iLine].Count;i++)
            {
                char c = lines[p.iLine][i].c;
                if (char.IsLetterOrDigit(c) || c=='_')
                    toX = i+1;
                else
                    break;
            }

            for (int i = p.iChar-1; i >=0; i--)
            {
                char c = lines[p.iLine][i].c;
                if (char.IsLetterOrDigit(c) || c=='_')
                    fromX = i;
                else
                    break;
            }

            Selection.Start = new Place(toX, p.iLine);
            Selection.End = new Place(fromX, p.iLine);

            Invalidate();
        }

        /// <summary>
        /// Gets nearest line and char position from coordinates
        /// </summary>
        /// <param name="point">Point</param>
        /// <returns>Line and char position</returns>
        public Place PointToPlace(Point point)
        {
            point.Offset(HorizontalScroll.Value, VerticalScroll.Value);
            point.Offset(-LeftIndent, 0);
            int y = 0;
            int iLine = 0;
            for (iLine=0; iLine<lines.Count; iLine++)
            {
                y += lines[iLine].WordWrapStringsCount * CharHeight;
                if (y > point.Y) break;
            }
            if (iLine >= lines.Count) iLine = lines.Count - 1;
            //
            int iWordWrapLine = lines[iLine].WordWrapStringsCount;
            do
            {
                iWordWrapLine--;
                y -= CharHeight;
            }while(y > point.Y);
            if (iWordWrapLine < 0) iWordWrapLine = 0;
            //
            int start = lines[iLine].GetWordWrapStringStartPosition(iWordWrapLine);
            int finish = lines[iLine].GetWordWrapStringFinishPosition(iWordWrapLine);
            int x = (int)Math.Round((float)point.X / CharWidth);
            x = x < 0 ? start : start + x;
            if (x > finish) 
                x = finish + 1;

            return new Place(x, iLine);
        }

        /// <summary>
        /// Gets nearest absolute text position for given point
        /// </summary>
        /// <param name="point">Point</param>
        /// <returns>Position</returns>
        public int PointToPosition(Point point)
        {
            return PlaceToPosition(PointToPlace(point));
        }

        /// <summary>
        /// Fires TextChanging event
        /// </summary>
        public void OnTextChanging()
        {
            if (TextChanging != null)
                TextChanging(this, new EventArgs());
        }

        /// <summary>
        /// Fires TextChanged event
        /// </summary>
        public void OnTextChanged()
        {
            Range r = new Range(this);
            r.SelectAll();
            OnTextChanged(new TextChangedEventArgs(r));
        }

        /// <summary>
        /// Fires TextChanged event
        /// </summary>
        public void OnTextChanged(int fromLine, int toLine)
        {
            Range r = new Range(this);
            r.Start = new Place(0, Math.Min(fromLine, toLine));
            r.End = new Place(lines[Math.Max(fromLine, toLine)].Count, Math.Max(fromLine, toLine));
            OnTextChanged(new TextChangedEventArgs(r));
        }

        /// <summary>
        /// Fires TextChanged event
        /// </summary>
        public void OnTextChanged(Range r)
        {
            OnTextChanged(new TextChangedEventArgs(r));
        }

        /// <summary>
        /// Fires TextChanged event
        /// </summary>
        protected void OnTextChanged(TextChangedEventArgs args)
        {
            IsChanged = true;
            TextVersion++;
            //
            if(wordWrap)
                RecalcWordWrap(args.ChangedRange.Start.iLine, args.ChangedRange.End.iLine);
            //
            base.OnTextChanged(args);
            
            if (TextChanged != null)
                TextChanged(this, args);
        }

        /// <summary>
        /// Fires SelectionCnaged event
        /// </summary>
        public void OnSelectionChanged()
        {
            if (SelectionChanged != null)
                SelectionChanged(this, new EventArgs());
        }

        protected override void OnGotFocus(EventArgs e)
        {
            base.OnGotFocus(e);
            //Invalidate(new Rectangle(PlaceToPoint(Selection.Start), new Size(2, CharHeight+1)));
            Invalidate();
        }

        protected override void OnLostFocus(EventArgs e)
        {
            base.OnLostFocus(e);
            //Invalidate(new Rectangle(PlaceToPoint(Selection.Start), new Size(2, CharHeight+1)));
            Invalidate();
        }

        /// <summary>
        /// Gets absolute text position from line and char position
        /// </summary>
        /// <param name="point">Line and char position</param>
        /// <returns>Index of text char</returns>
        public int PlaceToPosition(Place point)
        {
            if (point.iLine < 0 || point.iLine >= lines.Count || point.iChar >= lines[point.iLine].Count + Environment.NewLine.Length)
                return -1;

            int result = 0;
            for (int i = 0; i < point.iLine; i++)
                result += lines[i].Count + Environment.NewLine.Length;
            result += point.iChar;

            return result;
        }

        /// <summary>
        /// Gets line and char position from absolute text position
        /// </summary>
        /// <param name="pos"></param>
        /// <returns></returns>
        public Place PositionToPlace(int pos)
        {
            if (pos < 0)
                return new Place(0, 0);

            for (int i = 0; i < lines.Count; i++)
            {
                int lineLength = lines[i].Count + Environment.NewLine.Length;
                if (pos < lines[i].Count)
                    return new Place(pos, i);
                if (pos < lineLength)
                    return new Place(lines[i].Count, i);

                pos -= lineLength;
            }

            if(lines.Count>0)
                return new Place(lines[lines.Count-1].Count ,lines.Count-1);
            else
                return new Place(0, 0);
            //throw new ArgumentOutOfRangeException("Position out of range");
        }

        /// <summary>
        /// Gets point for given line and char position
        /// </summary>
        /// <param name="palce">Line and char position</param>
        /// <returns>Coordiantes</returns>
        public Point PlaceToPoint(Place place)
        {
            int y = 0;
            for (int iLine = 0; iLine <= place.iLine-1; iLine++)
                y += lines[iLine].WordWrapStringsCount*CharHeight;
            //
            int iWordWrapIndex = lines[place.iLine].GetWordWrapStringIndex(place.iChar);
            y += iWordWrapIndex * CharHeight;
            int x = (place.iChar - lines[place.iLine].GetWordWrapStringStartPosition(iWordWrapIndex)) * CharWidth;
            //
            y = y - VerticalScroll.Value;
            x = LeftIndent + x - HorizontalScroll.Value;

            return new Point(x, y);
        }

        /// <summary>
        /// Get range of text
        /// </summary>
        /// <param name="fromPos">Absolute start position</param>
        /// <param name="toPos">Absolute finish position</param>
        /// <returns>Range</returns>
        public Range GetRange(int fromPos, int toPos)
        {
            var sel = new Range(this);
            sel.Start = PositionToPlace(fromPos);
            sel.End = PositionToPlace(toPos);
            return sel;
        }

        /// <summary>
        /// Get range of text
        /// </summary>
        /// <param name="fromPlace">Line and char position</param>
        /// <param name="toPlace">Line and char position</param>
        /// <returns>Range</returns>
        public Range GetRange(Place fromPlace, Place toPlace)
        {
            var sel = new Range(this);
            sel.Start = fromPlace;
            sel.End = toPlace;
            return sel;
        }

        /// <summary>
        /// Finds ranges for given regex pattern
        /// </summary>
        /// <param name="regexPattern">Regex pattern</param>
        /// <returns>Enumeration of ranges</returns>
        public IEnumerable<Range> GetRanges(string regexPattern)
        {
            Range range = new Range(this);
            range.SelectAll();
            //
            foreach (var r in range.GetRanges(regexPattern, RegexOptions.None))
                yield return r;
        }

        /// <summary>
        /// Finds ranges for given regex pattern
        /// </summary>
        /// <param name="regexPattern">Regex pattern</param>
        /// <returns>Enumeration of ranges</returns>
        public IEnumerable<Range> GetRanges(string regexPattern, RegexOptions options)
        {
            Range range = new Range(this);
            range.SelectAll();
            //
            foreach (var r in range.GetRanges(regexPattern, options))
                yield return r;
        }

        /// <summary>
        /// Get text of given line
        /// </summary>
        /// <param name="iLine">Line index</param>
        /// <returns>Text</returns>
        public string GetLineText(int iLine)
        {
            if (iLine < 0 || iLine >= lines.Count)
                throw new ArgumentOutOfRangeException("Line index out of range");
            StringBuilder sb = new StringBuilder(lines[iLine].Count);
            foreach (Char c in lines[iLine])
                sb.Append(c.c);
            return sb.ToString();
        }
    }

    /// <summary>
    /// TextChanged event argument
    /// </summary>
    public class TextChangedEventArgs : EventArgs
    {
        /// <summary>
        /// This range contains changed area of text
        /// </summary>
        public Range ChangedRange { get; set; }

        /// <summary>
        /// Constructor
        /// </summary>
        public TextChangedEventArgs(Range changedRange)
        {
            this.ChangedRange = changedRange;
        }
    }
}
